﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace memcached
{
    /// <summary>
    /// Mem store only (in-proc), to simulate local cache instance.
    /// Closely simulates memcached behaviour.
    /// Thread-safe, but not very fast.
    /// </summary>
    public sealed class MemCacheMock : ICacheAdvanced
    {
        private readonly Dictionary<string, object> d = new Dictionary<string, object>(2);
        private readonly Dictionary<string, DateTime> expiry = new Dictionary<string, DateTime>(2);
        private readonly Dictionary<string, ulong> cas = new Dictionary<string, ulong>(2);
        private static long currentCas = 1;

        /// <summary></summary>
        public long Increment(string key, long startAt)
        {
            System.Diagnostics.Debug.Assert(key != null);
            System.Diagnostics.Debug.Assert(startAt >= 0);
            object v;
            lock (d)
            {
                if (d.TryGetValue(key, out v))
                {
                    if (v is long)
                        d[key] = (long)1 + (long)v;
                    else
                        if (v is string)
                            d[key] = (long)1 + Convert.ToInt64(v);
                }
                else
                {
                    d[key] = startAt;
                    expiry[key] = DateTime.MaxValue;
                }
                cas[key] = (ulong)System.Threading.Interlocked.Increment(ref currentCas);
                return (long)d[key];
            }
        }

        /// <summary></summary>
        public long Decrement(string key)
        {
            System.Diagnostics.Debug.Assert(key != null);
            object v;
            long? l = null;
            lock (d)
            {
                if (!d.TryGetValue(key, out v))
                    return -1;
                if (v is long)
                    l = (long)v;
                else
                    if (v is string)
                        l = Convert.ToInt64(v);
                if (l.HasValue && l.Value > 0)
                    d[key] = l.Value - 1;
                else
                    d[key] = (long)0;
                cas[key] = (ulong)System.Threading.Interlocked.Increment(ref currentCas);
                return (long)d[key];
            }
        }

        /// <summary></summary>
        public bool Remove(string key)
        {
            System.Diagnostics.Debug.Assert(key != null);
            lock (d)
            {
                d.Remove(key);
                expiry.Remove(key);
                cas.Remove(key);
            }
            return true;
        }

        /// <summary></summary>
        public bool Set(string key, object value)
        {
            System.Diagnostics.Debug.Assert(key != null);
            System.Diagnostics.Debug.Assert(value != null);
            lock (d)
            {
                d[key] = value;
                expiry[key] = DateTime.MaxValue;
                cas[key] = (ulong)System.Threading.Interlocked.Increment(ref currentCas);
            }
            return true;
        }

        /// <summary></summary>
        public bool Set(string key, int validFor, object value)
        {
            System.Diagnostics.Debug.Assert(key != null);
            System.Diagnostics.Debug.Assert(value != null);
            System.Diagnostics.Debug.Assert(validFor > 0);
            lock (d)
            {
                d[key] = value;
                expiry[key] = DateTime.Now.AddSeconds(validFor);
                cas[key] = (ulong)System.Threading.Interlocked.Increment(ref currentCas);
            }
            return true;
        }

        /// <summary></summary>
        public bool Set(string key, DateTime expireAt, object value)
        {
            System.Diagnostics.Debug.Assert(key != null);
            System.Diagnostics.Debug.Assert(value != null);
            System.Diagnostics.Debug.Assert(expireAt > DateTime.MinValue);
            lock (d)
            {
                d[key] = value;
                expiry[key] = expireAt;
                cas[key] = (ulong)System.Threading.Interlocked.Increment(ref currentCas);
            }
            return true;
        }

        /// <summary></summary>
        public bool Set(string key, ulong cas, object value)
        {
            System.Diagnostics.Debug.Assert(key != null);
            System.Diagnostics.Debug.Assert(value != null);
            ulong _cas;
            lock (d)
            {
                if (!this.cas.TryGetValue(key, out _cas) || _cas != cas)
                    return false;
                d[key] = value;
                expiry[key] = DateTime.MaxValue;
                this.cas[key] = (ulong)System.Threading.Interlocked.Increment(ref currentCas);
            }
            return true;
        }

        /// <summary></summary>
        public bool Add(string key, int validFor, object value)
        {
            System.Diagnostics.Debug.Assert(key != null);
            System.Diagnostics.Debug.Assert(value != null);
            System.Diagnostics.Debug.Assert(validFor > 0);
            DateTime exp;
            lock (d)
            {
                if (!expiry.TryGetValue(key, out exp) || exp < DateTime.Now)
                {
                    expiry[key] = DateTime.Now.AddSeconds(validFor);
                    d[key] = value;
                    cas[key] = (ulong)System.Threading.Interlocked.Increment(ref currentCas);
                    return true;
                }
                return false;
            }
        }

        /// <summary></summary>
        public bool Add(string key, DateTime expireAt, object value)
        {
            System.Diagnostics.Debug.Assert(key != null);
            System.Diagnostics.Debug.Assert(value != null);
            System.Diagnostics.Debug.Assert(expireAt > DateTime.MinValue);
            DateTime exp;
            lock (d)
            {
                if (!expiry.TryGetValue(key, out exp) || exp < DateTime.Now)
                {
                    expiry[key] = expireAt;
                    d[key] = value;
                    cas[key] = (ulong)System.Threading.Interlocked.Increment(ref currentCas);
                    return true;
                }
                return false;
            }
        }

        /// <summary></summary>
        public T Get<T>(string key, out bool success)
        {
            System.Diagnostics.Debug.Assert(key != null);
            success = true;
            object v;
            lock (d)
            {
                if (d.TryGetValue(key, out v) && v != null && v is T)
                {
                    if (expiry[key] > DateTime.Now)
                        return (T)v;
                    expiry.Remove(key);
                    cas.Remove(key);
                    d.Remove(key);
                }
            }
            return default(T);
        }

        /// <summary></summary>
        public object Get(string key, out bool success)
        {
            System.Diagnostics.Debug.Assert(key != null);
            success = true;
            object v;
            lock (d)
            {
                if (d.TryGetValue(key, out v) && v != null)
                {
                    if (expiry[key] > DateTime.Now)
                        return v;
                    expiry.Remove(key);
                    cas.Remove(key);
                    d.Remove(key);
                }
            }
            return null;
        }

        /// <summary></summary>
        public CasValue<T> Get<T>(string key)
        {
            System.Diagnostics.Debug.Assert(key != null);
            object v;
            lock (d)
            {
                if (d.TryGetValue(key, out v) && v != null && v is T)
                {
                    if (expiry[key] > DateTime.Now)
                    {
                        return new CasValue<T>((T)v, cas[key]);
                    }
                    expiry.Remove(key);
                    cas.Remove(key);
                    d.Remove(key);
                }
            }
            return null;
        }
    }
}